
package de.justphil.tcg.tcgandroidclient.content;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import de.justphil.tcg.tcgandroidclient.Example;
import de.justphil.tcg.tcgandroidclient.Storage;
import de.justphil.tcg.tcgandroidclient.content.rest.RESTCard;
import de.justphil.tcg.tcgandroidclient.content.rest.RESTCardInstance;
import de.justphil.tcg.tcgandroidclient.content.rest.RESTDeck;
import de.justphil.tcg.tcgandroidclient.content.sqlite.SqliteCard;
import de.justphil.tcg.tcgandroidclient.content.sqlite.SqliteCardInstance;
import de.justphil.tcg.tcgandroidclient.content.sqlite.SqliteDeck;
import de.justphil.tcg.tcgandroidclient.network.rest.RESTUpdateService;
import de.justphil.tcg.tcgandroidclient.util.Authorization;
import de.justphil.tcg.tcgandroidclient.util.TCGLog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;

public class TCGContentAdapter {
	
	private final ContentResolver cr;
	private final DH dataHelper;
	private final Context context;
	
	// private Map<String, Long> cache;
	public TCGContentAdapter(Context context) {
	
		// cache = new HashMap<String, Long>();
		cr = context.getContentResolver();
		dataHelper = new DH(context);
		this.context = context;
	}
	
	public void registerContentObserver(ContentObserver obs, Uri uri) {
	
		if (uri == null) {
			throw new IllegalArgumentException("Cannot register an observer without uri!");
		}
		if (obs != null) {
			cr.registerContentObserver(uri, true, obs);
		}
	}
	
	public void unregisterContentObserver(ContentObserver obs) {
	
		if (obs != null) {
			cr.unregisterContentObserver(obs);
		}
	}
	
	public Bitmap readProperty(int type, ContentObserver obs) {
	
		Uri uri = TCGUM.getPropertyIMGUri(type);
		Bitmap img = readImage(dataHelper.getFilePath(DH.FOLDER_PROPERTIES, type));
		registerContentObserver(obs, uri);
		if (img == null) {
			TCGLog.i(new Object[] {
					"CR Could not read property image ", type, "." });
		}
		else {
			TCGLog.d(new Object[] {
					"CR Read property image ", type, "." });
		}
		// String queryIdentifier = StringTools.getString(new Object[] {
		// "CR ", "property", "#", type });
		// if (cache.get(queryIdentifier) == null) {
		TCGLog.d(new Object[] {
				"CR ", "Property ", type, " must be updated." });
		Intent intent = new Intent(context, RESTUpdateService.class);
		intent.setData(uri);
		context.startService(intent);
		// TODO: only update cache if new data is actually written into the database.
		// cache.put(queryIdentifier, System.currentTimeMillis());
		// }
		// else {
		// TCGLog.d(new Object[] {
		// "CR ", "Property ", type, " is uptodate or an update is already running" });
		// }
		return img;
	}
	
	public void updateDeck(int id, int[] deck) {
	
	}
	
	public void addDeck(int id, String name, int[] deck) {
	
	}
	
	public CardData readCardData(int type, ContentObserver obs) {
	
		CardData card = new CardData(type);
		{
			Uri uri = TCGUM.getCardUri(type);
			Cursor c = cr.query(uri, null, null, null, null);
			registerContentObserver(obs, uri);
			if (c != null && c.moveToFirst()) {
				card.setName(c.getString(c.getColumnIndex(DH.C_CARD_NAME)));
				card.setLife(c.getInt(c.getColumnIndex(DH.C_CARD_LIFE)));
				card.setAttack(c.getInt(c.getColumnIndex(DH.C_CARD_ATTACK)));
				card.setDefense(c.getInt(c.getColumnIndex(DH.C_CARD_DEFENSE)));
				card.setAgility(c.getInt(c.getColumnIndex(DH.C_CARD_AGILITY)));
			}
			else {
				TCGLog.i(new Object[] {
						"CR ", "Could not read card data." });
			}
		}
		{
			Uri uri = TCGUM.getCardToPropertiesUri(type);
			Cursor c = cr.query(uri, null, null, null, null);
			registerContentObserver(obs, uri);
			int[] propertyTypes;
			if (c == null || c.getCount() == 0) {
				TCGLog.e(new Object[] {
						"CR ", "Could not read card propertyTypes for card of type ", type, "." });
				propertyTypes = new int[0];
			}
			else {
				propertyTypes = new int[c.getCount()];
				c.moveToFirst();
				for (int i = 0; i < c.getCount(); i++) {
					propertyTypes[i] = c.getInt(0);
					TCGLog.d(new Object[] {
							"CR ", "Read card property for card of type ", type, "." });
					c.moveToNext();
				}
			}
			card.setPropertyTypes(propertyTypes);
			TCGLog.d(new Object[] {
					"CR ", "Read RESTCard: ( Name: ", card.getName(), " [l: ", card.getLife(),
					" at: ", card.getAttack(), " d: ", card.getDefense(), " ag: ",
					card.getAgility(), "], Property count: ", card.getPropertyTypes().length, ")" });
		}
		{
			Bitmap img = readImage(dataHelper.getFilePath(DH.FOLDER_CARDS, type));
			Uri uri = TCGUM.getCardIMGUri(type);
			registerContentObserver(obs, uri);
			Intent intent = new Intent(context, RESTUpdateService.class);
			intent.setData(uri);
			context.startService(intent);
			if (img == null) {
				TCGLog.i(new Object[] {
						"CR ", "Could not read card image." });
			}
			else {
				TCGLog.d(new Object[] {
						"CR ", "Read card image." });
			}
			card.setImage(img);
		}
		return card;
	}
	
	public List<SqliteCardInstance> readCardInstances(ContentObserver obs) {
	
		Uri uri = TCGUM.getCardInstancesUri(Storage.getPlayerId());
		List<SqliteCardInstance> cards = new ArrayList<SqliteCardInstance>();
		Cursor c = query(uri, obs);
		int indexId = c.getColumnIndex(DH.C_CARD_ID);
		int indexType = c.getColumnIndex(DH.C_CARD_TYPE);
		for (int i = 0; i < c.getCount(); i++) {
			c.moveToNext();
			cards.add(new SqliteCardInstance(c.getLong(indexId), c.getInt(indexType)));
		}
		TCGLog.d(new Object[] {
				"Read ", cards.size(), " CardInstances." });
		return cards;
	}
	
	/**
	 * 
	 * @return the type to the cardId or 0 if not found.
	 */
	public int readCardInstance(long cardId, ContentObserver obs) {
	
		Uri uri = TCGUM.getCardInstanceUri(Storage.getPlayerId(),cardId);
		Cursor c = query(uri, obs);
		int type = 0;
		if (c.getCount() == 1) {
			c.moveToFirst();
			type = c.getInt(c.getColumnIndex(DH.C_CARD_TYPE));
		}
		TCGLog.d(new Object[] {
				"Read a CardInstance. Type: ", type });
		return type;
	}
	
	public Bitmap readCardImage(int cardType, ContentObserver obs) {
	
		Bitmap image = readImage(dataHelper.getFilePath(DH.FOLDER_IMAGES, cardType));
		Uri uri = TCGUM.getCardIMGUri(cardType);
		TCGLog.d(new Object[] {
			"Read a CardImage." });
		registerContentObserver(obs, uri);
		return image;
	}
	
	public Bitmap readPropertyImage(int propertyType, ContentObserver obs) {
	
		Bitmap image = readImage(dataHelper.getFilePath(DH.FOLDER_IMAGES, propertyType));
		Uri uri = TCGUM.getPropertyIMGUri(propertyType);
		TCGLog.d(new Object[] {
			"Read a PropertyImage." });
		registerContentObserver(obs, uri);
		return image;
	}
	
	public SqliteCard readCard(int cardType, ContentObserver obs) {
	
		Uri uri = TCGUM.getCardUri(cardType);
		Cursor c = query(uri, obs);
		int life = 0;
		int attack = 0;
		int defense = 0;
		int agility = 0;
		String name = null;
		if (c.getCount() == 1) {
			c.moveToFirst();
			life = c.getInt(c.getColumnIndex(DH.C_CARD_LIFE));
			attack = c.getInt(c.getColumnIndex(DH.C_CARD_ATTACK));
			defense = c.getInt(c.getColumnIndex(DH.C_CARD_DEFENSE));
			agility = c.getInt(c.getColumnIndex(DH.C_CARD_AGILITY));
			name = c.getString(c.getColumnIndex(DH.C_CARD_NAME));
		}
		TCGLog.d(new Object[] {
			"Read a Card." });
		return new SqliteCard(cardType, life, attack, defense, agility, name);
	}
	
	/**
	 * 
	 * @return the name of the deck
	 */
	public String readDeck(long cardId, ContentObserver obs) {
	
		Uri uri = TCGUM.getDeckUri(Storage.getPlayerId(),cardId);
		Cursor c = query(uri, obs);
		String name = null;
		if (c.getCount() == 1) {
			c.moveToFirst();
			name = c.getString(c.getColumnIndex(DH.C_DECK_NAME));
		}
		TCGLog.d(new Object[] {
			"Read a Deck." });
		return name;
	}
	
	public List<SqliteDeck> readDecks(ContentObserver obs) {
	
		Uri uri = TCGUM.getDecksUri(Storage.getPlayerId());
		Cursor c = query(uri, obs);
		List<SqliteDeck> decks = new ArrayList<SqliteDeck>();
		int count = c.getCount();
		int indexId = c.getColumnIndex(DH.C_DECK_ID);
		int indexName = c.getColumnIndex(DH.C_DECK_NAME);
		for (int i = 0; i < count; i++) {
			c.moveToNext();
			decks.add(new SqliteDeck(c.getLong(indexId), c.getString(indexName)));
		}
		TCGLog.d(new Object[] {
				"Read ", decks.size(), " Decks." });
		return decks;
	}
	
	public long[] readDeckToCards(long deckId, ContentObserver obs) {
	
		Uri uri = TCGUM.getDeckToCardsUri(deckId);
		Cursor c = query(uri, obs);
		long[] cardIds = new long[c.getCount()];
		int indexId = c.getColumnIndex(DH.C_CARD_ID);
		for (int i = 0; i < cardIds.length; i++) {
			c.moveToNext();
			cardIds[i] = c.getLong(indexId);
		}
		if (cardIds.length == 0) {
			TCGLog.w(new Object[] {
					"No CardToDeck entries found for deck ", deckId, "!" });
		}
		TCGLog.d(new Object[] {
				"Read ", cardIds.length, " DeckToCards." });
		return cardIds;
	}
	
	public void writePropertyImg(int type, byte[] bytes) {
	
		writePNGImage(dataHelper.getFilePath(DH.FOLDER_PROPERTIES, type), bytes);
		// TODO: perhaps put notification triggering into the content provider.
		TCGLog.d(new Object[] {
			"Wrote a Property Image." });
		cr.notifyChange(TCGUM.getPropertyIMGUri(type), null);
	}
	
	public void writeCardImg(int type, byte[] bytes) {
	
		writePNGImage(dataHelper.getFilePath(DH.FOLDER_CARDS, type), bytes);
		// TODO: perhaps put notification triggering into the content provider.
		TCGLog.d(new Object[] {
			"Wrote a Card Image." });
		cr.notifyChange(TCGUM.getPropertyIMGUri(type), null);
	}
	
	public void writeCard(int cardType, RESTCard card) {
	
		if (cardType <= 0) {
			throw new IllegalArgumentException("The cardType must be > 0!");
		}
		/* Updating Card */
		{
			Uri uri = TCGUM.getCardUri(cardType);
			ContentValues values = new ContentValues();
			values.put(DH.C_CARD_NAME, card.getName());
			values.put(DH.C_CARD_LIFE, card.getLife());
			values.put(DH.C_CARD_ATTACK, card.getAttack());
			values.put(DH.C_CARD_DEFENSE, card.getDefense());
			values.put(DH.C_CARD_AGILITY, card.getAgility());
			if (cr.update(uri, values, null, null) == 0) {
				if (cr.insert(uri, values) == null) {
					throw new RuntimeException("Could not write card!");
				}
				TCGLog.d(new Object[] {
					"Inserted a card." });
			}
			else {
				TCGLog.d(new Object[] {
					"Updated a card." });
			}
		}
		/* Deleting and inserting Card to Properties */
		{
			Uri uri = TCGUM.getCardToPropertiesUri(cardType);
			int deleted = cr.delete(uri, null, null);
			if (deleted > 0) {
				TCGLog.d(new Object[] {
						"Deleted ", deleted, " decks." });
			}
			int[] propertyTypes = card.getPropertyTypes();
			for (int propertyType : propertyTypes) {
				ContentValues values = new ContentValues();
				values.put(DH.C_PROPERTY_TYPE, propertyType);
				cr.insert(uri, values);
			}
			TCGLog.d(new Object[] {
					"Inserted ", propertyTypes.length, " cardToProperties." });
		}
	}
	
	public void writeDecks(List<RESTDeck> decks) {
	
		if (decks == null) {
			throw new IllegalArgumentException("The decks must not be null!");
		}
		/* Deletig and inserting */
		{
			Uri uri = TCGUM.getDecksUri(Storage.getPlayerId());
			long playerId = TCGUM.getLongId(0, uri);
			int deletedCards = cr.delete(uri, null, null);
			int deletedDecksToCards = cr.delete(TCGUM.getDecksToCardsUri(), null, null);
			if (deletedDecksToCards > 0) {
				TCGLog.d(new Object[] {
						"Deleted ", deletedDecksToCards, " deckToCards." });
			}
			if (deletedCards > 0) {
				TCGLog.d(new Object[] {
						"Deleted ", deletedCards, " decks." });
			}
			for (RESTDeck deck : decks) {
				{
					ContentValues values = new ContentValues();
					values.put(DH.C_DECK_NAME, deck.getName());
					values.put(DH.C_DECK_ID, deck.getDeckId());
					values.put(DH.C_PLAYER_ID, playerId);
					cr.insert(uri, values);
				}
				Uri deckToCardsUri = TCGUM.getDeckToCardsUri(deck.getDeckId());
				for (long cardId : deck.getCardIds()) {
					ContentValues values = new ContentValues();
					values.put(DH.C_CARD_ID, cardId);
					cr.insert(deckToCardsUri, values);
				}
			}
			TCGLog.d(new Object[] {
					"Inserted ", decks.size(), " decks." });
		}
	}
	
	public void writeCardInstances(List<RESTCardInstance> cards) {
	
		if (cards == null) {
			throw new IllegalArgumentException("The cards must not be null!");
		}
		/* Deletig and inserting */
		{
			{
				Uri uri = TCGUM.getCardInstancesUri(Storage.getPlayerId());
				int deleted = cr.delete(uri, null, null);
				if (deleted > 0) {
					TCGLog.d(new Object[] {
							"Deleted ", deleted, " card instances." });
				}
			}
			{
				for (RESTCardInstance card : cards) {
					Uri uri = TCGUM.getCardInstanceUri(Storage.getPlayerId(),card.getCardId());
					ContentValues values = new ContentValues();
					values.put(DH.C_CARD_TYPE, card.getCardType());
					cr.insert(uri, values);
				}
				TCGLog.d(new Object[] {
						"Inserted ", cards.size(), " card instances." });
			}
		}
	}
	
	private Cursor query(Uri uri, ContentObserver obs) {
	
		Cursor c = cr.query(uri, null, null, null, null);
		if (c == null) {
			throw new RuntimeException("Could not query Uri '" + uri + "'!");
		}
		registerContentObserver(obs, uri);
		return c;
	}
	
	private Bitmap readImage(String path) {
	
		return BitmapFactory.decodeFile(path);
	}
	
	private void writePNGImage(String path, byte[] bytes) {
	
		try {
			File f = new File(path);
			if (!f.exists()) {
				f.createNewFile();
				f.setWritable(true);
			}
			new FileOutputStream(f);
			FileOutputStream out = new FileOutputStream(f);
			out.write(bytes);
			out.flush();
			out.close();
			TCGLog.d(new Object[] {
					"CR ", "Wrote an image to path ", path, " using ", f.getTotalSpace(), " bytes." });
		}
		catch (IOException e) {
			TCGLog.e(new Object[] {
					"CR ", "Failed writing an image to path ", path, "!" }, e);
		}
	}
	
	public String getAuthorization() {
	
		return Authorization.getAuthorization(Storage.getUserName(), Storage.getPwd());
	}
}
